package cn.zouchanglin.spider_xpu.core;

import cn.zouchanglin.spider_xpu.contant.SpiderConstant;
import cn.zouchanglin.spider_xpu.SpiderResult;
import cn.zouchanglin.spider_xpu.cache.ResultCache;
import cn.zouchanglin.spider_xpu.entity.Course;
import cn.zouchanglin.spider_xpu.entity.Student;
import cn.zouchanglin.spider_xpu.exception.LoginException;
import cn.zouchanglin.spider_xpu.utils.UrlEncodeUtils;
import lombok.AllArgsConstructor;
import okhttp3.*;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;


/**
 * 爬虫核心实现
 * @author zouchanglin
 */
public class SpiderCore {
    private SpiderCore(){}
    private static final ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
    private static final OkHttpClient okHttpClient = new OkHttpClient();
    private static final ConcurrentHashMap<String, TmpSpiderData> tmpSpiderKV = new ConcurrentHashMap<>();

    /**
     * 获取验证码 URL
     * @param key 用户唯一主键
     * @return URL
     */
    public static String getCheckCodeUrl(String key){
        Request request = new Request.Builder().get().url(SpiderConstant.BASE_URL).build();
        try {
            Response execute = okHttpClient.newCall(request).execute();
            String newUrl = execute.request().url().toString();
            String sessionCode = newUrl.substring(23, newUrl.length() - 15);
            String checkCodeUrl = String.format("%s/(%s)/CheckCode.aspx", SpiderConstant.BASE_URL, sessionCode);
            tmpSpiderKV.put(key, new TmpSpiderData(sessionCode, newUrl, ""));
            return checkCodeUrl;
        } catch (IOException e){
            e.printStackTrace();
        }
        return "ERROR!";
    }

    /**
     * 此方法用于同步获取用户学生信息 + 当年学年的课表信息
     * @param studentId 学生学号
     * @param password 教务系统密码
     * @param checkCode 验证码
     * @param key 唯一主键
     * @return 爬取结果通用对象（同步调用结果 + 异步缓存结果） {@link SpiderResult}
     * @throws Exception 登录失败会抛出异常 {@link LoginException}
     */
    public static SpiderResult go(String studentId, String password, String checkCode, String key) throws Exception {
        SpiderResult spiderResult = new SpiderResult();
        spiderResult.setKey(key);

        TmpSpiderData tmpSpiderData = tmpSpiderKV.get(key);
        if(tmpSpiderData == null) throw new RuntimeException("TmpSpiderData cache error!");

        Document document = Jsoup.parse(new URL(tmpSpiderData.newUrl), 5000);
        String __VIEWSTATE = document.select("form#form1 > input").first().attr("value");
        // 更新__VIEWSTATE
        tmpSpiderData.__VIEWSTATE = __VIEWSTATE;
        tmpSpiderKV.put(key, tmpSpiderData);

        RequestBody requestBody = new FormBody.Builder()
                .add("__VIEWSTATE", __VIEWSTATE)
                .add("txtUserName", studentId)
                .add("TextBox2", password)
                .add("txtSecretCode", checkCode)
                .add("RadioButtonList1","(unable to decode value)")
                .add("Button1", "")
                .add("lbLanguage", "")
                .add("hidPdrs", "")
                .add("hidsc", "")
                .build();
        String loginUrl = String.format("%s/(%s)/default2.aspx", SpiderConstant.BASE_URL, tmpSpiderData.sessionCode);
        Request request = new Request.Builder().post(requestBody).url(loginUrl).build();
        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
        String studentMainUrl = response.request().url().toString();
        request = new Request.Builder().get().url(studentMainUrl).build();
        Response execute = okHttpClient.newCall(request).execute();

        assert execute.body() != null;
        String studentMainBody = execute.body().string();
        Document mainDoc = Jsoup.parse(studentMainBody);

        if("欢迎使用正方教务管理系统！请登录".equals(mainDoc.select("title").first().text())){
            throw new LoginException("请核对用户名、密码、验证码等数据");
        }
        spiderResult.setStudent(getStudentInfo(mainDoc, tmpSpiderData.sessionCode, studentMainUrl));
        getCourseInfo(mainDoc, tmpSpiderData.sessionCode, studentMainUrl, spiderResult);
        executorService.execute(()->{
            try {
                getAllCourseInfo(mainDoc, tmpSpiderData.sessionCode, studentMainUrl, spiderResult, key);
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                tmpSpiderKV.remove(key);
            }
        });
        return spiderResult;
    }

    /**
     * 获取当前学年的课表
     */
    private static void getCourseInfo(Document mainDoc, String sessionCode, String studentMainUrl, SpiderResult spiderResult) throws IOException {
        String courseChildUrl = mainDoc.select("[onclick=GetMc('学生个人课表');]").first().attr("href");
        String courseUrl = String.format("%s/(%s)/%s", SpiderConstant.BASE_URL, sessionCode, URLDecoder.decode(courseChildUrl, "GB2312"));
        Request request = new Request.Builder()
                .addHeader("Referer", studentMainUrl)
                .addHeader("Connection", "keep-alive")
                .addHeader("Accept-Encoding", "gzip, deflate")
                .addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
                .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36")
                .get()
                .url(courseUrl).build();
        Call call = okHttpClient.newCall(request);
        Response execute = call.execute();
        String courseMainBody = new String(execute.body().bytes(), "GB2312");
        Document courseMainDoc = Jsoup.parse(courseMainBody);
        spiderResult.setCourse(parseCourse(courseMainDoc));
    }


    /**
     * 获取所有学年的课表
     */
    private static void getAllCourseInfo(Document mainDoc, String sessionCode, String studentMainUrl, SpiderResult spiderResult, String key) throws IOException {
        String courseChildUrl = mainDoc.select("[onclick=GetMc('学生个人课表');]").first().attr("href");
        String courseUrl = String.format("%s/(%s)/%s", SpiderConstant.BASE_URL, sessionCode, URLDecoder.decode(courseChildUrl, "GB2312"));
        Request request = new Request.Builder()
                .addHeader("Referer", studentMainUrl)
                .addHeader("Connection", "keep-alive")
                .addHeader("Accept-Encoding", "gzip, deflate")
                .addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
                .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36")
                .get()
                .url(courseUrl).build();
        Call call = okHttpClient.newCall(request);
        Response execute = call.execute();
        assert execute.body() != null;
        String courseMainBody = new String(execute.body().bytes(), "GB2312");
        //===============解析课表=============
        Document courseMainDoc = Jsoup.parse(courseMainBody);
        spiderResult.setCourse(parseCourse(courseMainDoc));

        System.out.println("=========================================");
        //==============选择学年===============
        Elements options = courseMainDoc.select("[name=xnd]").first().getElementsByTag("option");
        String __VIEWSTATE = courseMainDoc.select("form#xskb_form [name=__VIEWSTATE]").first().attr("value");
        // 拿到可选的学年
        ArrayList<String> yearList = new ArrayList<>();
        for(Element option: options){
            String text = option.text();
            if(!text.contains("?")){
                yearList.add(text);
            }
        }

        String[] years = new String[yearList.size()];
        yearList.toArray(years);
        spiderResult.setYears(years);
        ConcurrentHashMap<String, List<Course>> map = new ConcurrentHashMap<>();
        for (int k = years.length - 1; k >= 0; k--) {
            for (int i = 1; i <= 2; i++) {
                courseUrl = UrlEncodeUtils.urlEncodeChinese(courseUrl);
                RequestBody requestBody = new FormBody.Builder()
                        .add("__EVENTTARGET", "xnd")
                        .add("__EVENTARGUMENT", "")
                        .add("__VIEWSTATE", __VIEWSTATE)
                        .add("xnd", years[k])
                        .add("xqd", String.valueOf(i))
                        .build();
                request = new Request.Builder()
                        .addHeader("Referer", courseUrl)
                        .addHeader("Accept", "*/*")
                        .addHeader("Accept-Encoding", "gzip, deflate")
                        .addHeader("Content-Type", "application/x-www-form-urlencoded")
                        .addHeader("User-Agent", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
                        .addHeader("Host", SpiderConstant.BASE_IP)
                        .addHeader("Origin", SpiderConstant.BASE_URL)
                        .addHeader("Connection", "keep-alive")
                        .post(requestBody)
                        .url(courseUrl).build();
                Response response = okHttpClient.newCall(request).execute();
                assert response.body() != null;
                String body = new String(response.body().bytes(), "GB2312");
                //更新__VIEWSTATE
                __VIEWSTATE = Jsoup.parse(body).select("form#xskb_form [name=__VIEWSTATE]").first().attr("value");
                Document courseMainDoc2 = Jsoup.parse(body);
                List<Course> courses = parseCourse(courseMainDoc2);
                map.put(years[k] + " " + i, courses);
            }
        }
        spiderResult.setCourses(map);
        tmpSpiderKV.remove(key);
        // 结果放入缓存池，等待持久化
        ResultCache.SPIDER_RESULT_CACHE.put(key, spiderResult);
    }

    /**
     * 解析课表
     */
    private static List<Course> parseCourse(Document courseMainDoc) {
        courseMainDoc.select("table.blacktab > tbody");
        ArrayList<Course> retCourses = new ArrayList<>();
        Element tbody = courseMainDoc.select("table.blacktab > tbody").first();
        Elements tds = tbody.select("tr td");
        for(Element td: tds){
            String tdContent = td.text();
            if(tdContent.contains("{")){
                try {
                    String tdString = td.toString();
                    int start = tdString.indexOf(">") + 1;
                    int end = tdString.indexOf("</td>");
                    String content = tdString.substring(start, end);
                    String[] contentArray = content.split("<br>");
                    Course course = new Course(contentArray[0],
                            contentArray[1].substring(contentArray[1].indexOf("{") + 2, contentArray[1].indexOf("}") - 1),
                            contentArray[2],
                            contentArray[1].substring(contentArray[1].indexOf("第") + 1, contentArray[1].indexOf("节")),
                            contentArray[1].substring(0, 2));
                    if(contentArray.length >= 4){
                        course.setCourseRoom(contentArray[3]);
                    }
                    retCourses.add(course);
                }catch (ArrayIndexOutOfBoundsException e){
                    System.out.println("ArrayIndexOutOfBoundsException " + tdContent);
                }
            }
        }
        return retCourses;
    }

    /**
     * 获取学生用户信息
     */
    private static Student getStudentInfo(Document mainDoc, String sessionCode, String studentMainUrl) throws IOException {
        String info = mainDoc.select("[onclick=GetMc('个人信息');]").first().attr("href");
        String infoUrl = String.format("%s/(%s)/%s", SpiderConstant.BASE_URL, sessionCode, info);
        Request request = new Request.Builder()
                .get()
                .addHeader("Referer", studentMainUrl)
                .addHeader("Connection", "keep-alive")
                .addHeader("Accept-Encoding", "gzip, deflate")
                .addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
                .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36")
                .url(infoUrl).build();
        Response execute = okHttpClient.newCall(request).execute();
        assert execute.body() != null;
        String StudentInfoBody = new String(execute.body().bytes(), "GB2312");

        Document studentInfoBody = Jsoup.parse(StudentInfoBody);
        Element sex = studentInfoBody.select("[id=XB]").first();
        Student stu = new Student(studentInfoBody.select("[id=xh]").first().text(),
                studentInfoBody.select("[id=xm]").first().text(),
                sex.select("[selected=selected]").first().attr("value"),
                studentInfoBody.select("[id=csrq]").first().attr("value"),
                studentInfoBody.select("[id=txtjg]").first().attr("value"),
                studentInfoBody.select("[id=sfzh]").first().attr("value"),
                studentInfoBody.select("[id=xzb]").first().attr("value"),
                studentInfoBody.select("[id=xy]").first().attr("value"),
                studentInfoBody.select("[id=zymc]").first().attr("value"),
                ""
        );
        String phone = studentInfoBody.select("[id=lxdh]").first().attr("value");
        if(phone != null) stu.setPhone(phone);
        return stu;
    }

    @AllArgsConstructor
    private static class TmpSpiderData {
        String sessionCode;
        String newUrl;
        String __VIEWSTATE;
    }
}
